﻿/*
	This file is part of Limpet.

    Limpet is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Limpet is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Limpet.  If not, see http://www.gnu.org/licenses/.
	
	Copyright 2010 Dan Popick
*/
using System;
using System.Collections.Generic;
using System.Configuration;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web;
using System.Xml;

namespace Open.Web.Diagnostics.Limpet
{
    /// <summary>
    /// Configuration handler for Limpet data
    /// </summary>
    /// <remarks>
    /// I've fallen back to the .NET 1.1 configuration module because it allows a more
    /// natural access to the XML structure of the configuration. This facilitates passing
    /// the XML Node of each module into the module constructor, allowing more rich config
    /// options for modules.
    /// </remarks>
    public class LimpetConfigHandler : IConfigurationSectionHandler
    {
        // helper class for reading module config data
        class LimpetModuleConfig
        {
            public string Name { get; set; }
            public string Type { get; set; }
            public string Settings { get; set; }
        }

        /// <summary>
        /// Creates a configuration object
        /// </summary>
        /// <param name="parent">Unused</param>
        /// <param name="configContext">Unused</param>
        /// <param name="section">The XML node for the Limpet configuration section</param>
        /// <returns>A LimpetConfig object represented by the config XML data</returns>
        public object Create(object parent, object configContext, System.Xml.XmlNode section)
        {
            LimpetConfig config = new LimpetConfig();

            AssignAttributeProperties(config, section);

            foreach (XmlNode addModule in section.SelectNodes("modules/add"))
            {
                // convert the config data to modules
                ILimpetModule mod = ReadModuleConfig(addModule);
                config.Modules.Add(mod);
            }

            foreach (XmlNode allowedIpNode in section.SelectNodes("allowedIP"))
            {
                IPRestriction rest = new IPRestriction();
                AssignAttributeProperties(rest, allowedIpNode);
                config.AllowedAddresses.Add(rest);
            }

            return config;
        }

        /// <summary>
        /// Converts the limpet config data into a concrete module
        /// </summary>
        /// <param name="node">The XML node containing the configuration for the module</param>
        /// <returns>A Limpet module</returns>
        ILimpetModule ReadModuleConfig(XmlNode node)
        {
            LimpetModuleConfig config = new LimpetModuleConfig();

            AssignAttributeProperties(config, node);

            Type type = Type.GetType(config.Type);
            ILimpetModule module = null;
            if (type != null)
                module = Activator.CreateInstance(type, config.Name, node) as ILimpetModule;

            if (module != null)
                AssignAttributeProperties(module, node);

            return module;
        }

        /// <summary>
        /// Assigns the data in XML attributes to properties in the passed object
        /// </summary>
        /// <param name="obj">The object to which the attributes are assigned</param>
        /// <param name="node">The XML node containing the data</param>
        void AssignAttributeProperties(object obj, XmlNode node)
        {
            foreach (PropertyInfo pi in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                foreach (XmlAttribute attr in node.Attributes)
                {
                    if (String.Compare(attr.Name, pi.Name, true) != 0)
                        continue;

                    MethodInfo convertMethod = null;
                    object value;

                    foreach (MethodInfo mi in pi.PropertyType.GetMethods(BindingFlags.Public | BindingFlags.Static))
                    {
                        if (IsSimpleTryParse(pi, mi))
                        {
                            convertMethod = mi;
                            break;
                        }
                    }

                    if (convertMethod != null)
                    {
                        // there's a tryparse for this type, try to use that
                        object[] parameters = new object[] { attr.Value, null };
                        bool result = (bool)convertMethod.Invoke(null, parameters);

                        if (!result)
                        {
                            System.Diagnostics.Debug.WriteLine(String.Format("Call to {0}.TryParse returned false for {1}.", pi.PropertyType.Name, attr.Value));
                            continue;
                        }
                        else
                            value = parameters[1];
                    }
                    else if (pi.PropertyType.IsEnum)
                    {
                        value = Enum.Parse(pi.PropertyType, attr.Value);
                    }
                    else
                        // otherwise go with Convert
                        value = Convert.ChangeType(attr.Value, pi.PropertyType);

                    if (attr != null)
                        pi.SetValue(obj, value, null);
                }
            }
        }

        private static bool IsSimpleTryParse(PropertyInfo pi, MethodInfo mi)
        {
            ParameterInfo[] methParams = mi.GetParameters();

            return mi.Name == "TryParse"
                && methParams.Length == 2
                && mi.ReturnType == typeof(bool)
                && methParams[0].ParameterType == typeof(String)
                && methParams[1].ParameterType.GetElementType() == pi.PropertyType
                && methParams[1].IsOut;
        }
    }

}
